<?php

namespace ezswoole;

use ezswoole\exception\ClassNotFoundException;
use ezswoole\validator\ValidatorRule;

class Validator
{
	// 实例
	protected static $instance;
	// 验证类型别名
	protected $alias
		= [
			'>'    => 'gt',
			'>='   => 'egt',
			'<'    => 'lt',
			'<='   => 'elt',
			'='    => 'eq',
			'same' => 'eq',
		];

	// 当前验证的规则
	protected $rule = [];

	// 验证提示信息
	protected $message = [];


	// 验证字段描述
	protected $field = [];

	// 验证错误码描述
	protected $code = [];


	// 验证规则默认提示信息
	protected static $typeMsg
		= [
			'require'     => ':attribute不能为空',
			'must'        => ':attribute must',
			'number'      => ':attribute必须是数字',
			'integer'     => ':attribute必须是整数',
			'float'       => ':attribute必须是浮点数',
			'boolean'     => ':attribute必须是布尔值',
			'email'       => ':attribute格式不符',
			'array'       => ':attribute必须是数组',
			'arrayUnique' => ':attribute不可有重复值',
			'json'        => ':attribute不是json',
			'phone'       => ':attribute格式不符',
			'accepted'    => ':attribute必须是yes、on或者1',
			'date'        => ':attribute格式不符合',
			'file'        => ':attribute不是有效的上传文件',
			'image'       => ':attribute不是有效的图像文件',
			'alpha'       => ':attribute只能是字母',
			'alphaNum'    => ':attribute只能是字母和数字',
			'alphaDash'   => ':attribute只能是字母、数字和下划线_及破折号-',
			'activeUrl'   => ':attribute不是有效的域名或者IP',
			'chs'         => ':attribute只能是汉字',
			'chsAlpha'    => ':attribute只能是汉字、字母',
			'chsAlphaNum' => ':attribute只能是汉字、字母和数字',
			'chsDash'     => ':attribute只能是汉字、字母、数字和下划线_及破折号-',
			'url'         => ':attribute不是有效的URL地址',
			'ip'          => ':attribute不是有效的IP地址',
			'dateFormat'  => ':attribute必须使用日期格式 :rule',
			'in'          => ':attribute必须在 :rule 范围内',
			'notIn'       => ':attribute不能在 :rule 范围内',
			'between'     => ':attribute只能在 :1 - :2 之间',
			'notBetween'  => ':attribute不能在 :1 - :2 之间',
			'length'      => ':attribute长度不符合要求 :rule',
			'max'         => ':attribute长度不能超过 :rule',
			'min'         => ':attribute长度不能小于 :rule',
			'after'       => ':attribute日期不能小于 :rule',
			'before'      => ':attribute日期不能超过 :rule',
			'allowIp'     => '不允许的IP访问',
			'denyIp'      => '禁止的IP访问',
			'confirm'     => ':attribute和确认字段:2不一致',
			'different'   => ':attribute和比较字段:2不能相同',
			'egt'         => ':attribute必须大于等于 :rule',
			'gt'          => ':attribute必须大于 :rule',
			'elt'         => ':attribute必须小于等于 :rule',
			'lt'          => ':attribute必须小于 :rule',
			'eq'          => ':attribute必须等于 :rule',
			'unique'      => ':attribute已存在',
			'regex'       => ':attribute不符合指定规则',
			'method'      => '无效的请求类型',
			'token'       => '令牌数据无效',
			'fileSize'    => '上传文件大小不符',
			'fileExt'     => '上传文件后缀不符',
			'fileMime'    => '上传文件类型不符',
		];

	// 当前验证场景
	protected $currentSceneName = null;

	/**
	 * 内置正则验证规则
	 * @var array
	 */
	protected $regex
		= [
			'alpha'       => '/^[A-Za-z]+$/',
			'alphaNum'    => '/^[A-Za-z0-9]+$/',
			'alphaDash'   => '/^[A-Za-z0-9\-\_]+$/',
			'chs'         => '/^[\x{4e00}-\x{9fa5}]+$/u',
			'chsAlpha'    => '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u',
			'chsAlphaNum' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u',
			'chsDash'     => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u',
			'mobile'      => '/^1[3-9][0-9]\d{8}$/',
			'idCard'      => '/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}$)/',
			'zip'         => '/\d{6}/',
		];


	/**
	 * Filter_var 规则
	 * @var array
	 */
	protected $filter
		= [
			'email'   => FILTER_VALIDATE_EMAIL,
			'ip'      => [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6],
			'integer' => FILTER_VALIDATE_INT,
			'url'     => FILTER_VALIDATE_URL,
			'macAddr' => FILTER_VALIDATE_MAC,
			'float'   => FILTER_VALIDATE_FLOAT,
		];

	/**
	 * 验证场景定义
	 * @var array
	 */
	protected $scene = [];

	// 验证失败错误信息
	protected $error = [];

	// 批量验证
	protected $batch = false;

	/**
	 * 场景需要验证的规则
	 * @var array
	 */
	protected $only = [];
	/**
	 * 场景需要移除的验证规则
	 * @var array
	 */
	protected $remove = [];
	/**
	 * 场景需要追加的验证规则
	 * @var array
	 */
	protected $append = [];

	/**
	 * 构造函数
	 * @access public
	 * @param array $rules   验证规则
	 * @param array $message 验证提示信息
	 * @param array $field   验证字段描述信息
	 */
	public function __construct( array $rules = [], $message = [], $field = [] )
	{
		$this->rule    = array_merge( $this->rule, $rules );
		$this->message = array_merge( $this->message, $message );
		$this->field   = array_merge( $this->field, $field );
	}

	/**
	 * 实例化验证
	 * @access public
	 * @param array $rules   验证规则
	 * @param array $message 验证提示信息
	 * @param array $field   验证字段描述信息
	 * @return Validator
	 */
	public static function make( $rules = [], $message = [], $field = [] )
	{
		if( is_null( self::$instance ) ){
			self::$instance = new self( $rules, $message, $field );
		}
		return self::$instance;
	}

	/**
	 * 添加字段验证规则
	 * @param string|array $name 字段名称或者规则数组
	 * @param mixed        $rule 验证规则
	 * @return Validator
	 */
	public function rule( $name, $rule = '' )
	{
		if( is_array( $name ) ){
			$this->rule = array_merge( $this->rule, $name );
			if( is_array( $rule ) ){
				$this->field = array_merge( $this->field, $rule );
			}
		} else{
			$this->rule[$name] = $rule;
		}
		return $this;
	}

	/**
	 * 注册扩展验证（类型）规则
	 * @access public
	 * @param  string $type     验证规则类型
	 * @param  mixed  $callback callback方法(或闭包)
	 * @return void
	 */
	public static function extend( $type, $callback = null )
	{
		if( is_array( $type ) ){
			self::$type = array_merge( self::$type, $type );
		} else{
			self::$type[$type] = $callback;
		}
	}

	/**
	 * 设置验证规则的默认提示信息
	 * @access public
	 * @param  string|array $type 验证规则类型名称或者数组
	 * @param  string       $msg  验证提示信息
	 * @return void
	 */
	public static function setTypeMsg( $type, $msg = null )
	{
		if( is_array( $type ) ){
			self::$typeMsg = array_merge( self::$typeMsg, $type );
		} else{
			self::$typeMsg[$type] = $msg;
		}
	}

	/**
	 * 设置提示信息
	 * @access public
	 * @param  string|array $name    字段名称
	 * @param  string       $message 提示信息
	 * @return Validator
	 */
	public function message( $name, $message = '' )
	{
		if( is_array( $name ) ){
			$this->message = array_merge( $this->message, $name );
		} else{
			$this->message[$name] = $message;
		}
		return $this;
	}

	/**
	 * 设置验证场景
	 * @access public
	 * @param  string $name 场景名
	 * @return $this
	 */
	public function scene( $name )
	{
		$this->currentSceneName = $name;
		return $this;
	}

	public function getCurrentSceneName() : string
	{
		return $this->currentSceneName;
	}


	/**
	 * 判断是否存在某个验证场景
	 * @access public
	 * @param  string $name 场景名
	 * @return bool
	 */
	public function hasScene( $name )
	{
		return isset( $this->scene[$name] ) || method_exists( $this, 'scene'.$name );
	}


	/**
	 * 设置批量验证
	 * @access public
	 * @param  bool $batch 是否批量验证
	 * @return $this
	 */
	public function batch( $batch = true )
	{
		$this->batch = $batch;
		return $this;
	}

	/**
	 * 指定需要验证的字段列表
	 * @access public
	 * @param  array $fields 字段名
	 * @return $this
	 */
	public function only( $fields )
	{
		$this->only = $fields;
		return $this;
	}


	/**
	 * 移除某个字段的验证规则
	 * @access public
	 * @param  string|array $field 字段名
	 * @param  mixed        $rule  验证规则 true 移除所有规则
	 * @return $this
	 */
	public function remove( $field, $rule = true )
	{
		if( is_array( $field ) ){
			foreach( $field as $key => $rule ){
				if( is_int( $key ) ){
					$this->remove( $rule );
				} else{
					$this->remove( $key, $rule );
				}
			}
		} else{
			if( is_string( $rule ) ){
				$rule = explode( '|', $rule );
			}
			$this->remove[$field] = $rule;
		}
		return $this;
	}

	/**
	 * 追加某个字段的验证规则
	 * @access public
	 * @param  string|array $field 字段名
	 * @param  mixed        $rule  验证规则
	 * @return $this
	 */
	public function append( $field, $rule = null )
	{
		if( is_array( $field ) ){
			foreach( $field as $key => $rule ){
				$this->append( $key, $rule );
			}
		} else{
			if( is_string( $rule ) ){
				$rule = explode( '|', $rule );
			}
			$this->append[$field] = $rule;
		}
		return $this;
	}

	/**
	 * 数据自动验证
	 * @access public
	 * @param  array  $data  数据
	 * @param  mixed  $rules 验证规则
	 * @param  string $scene 验证场景
	 * @return bool
	 */
	public function check( $data, $rules = [], $scene = '' )
	{
		$this->error = [];
		if( empty( $rules ) ){
			// 读取验证规则
			$rules = $this->rule;
		}
		// 获取场景定义
		$this->getScene( $scene );

		foreach( $this->append as $key => $rule ){
			if( !isset( $rules[$key] ) ){
				$rules[$key] = $rule;
			}
		}

		foreach( $rules as $key => $rule ){
			if( strpos( $key, '|' ) ){
				// 字段|描述 用于指定属性名称
				list( $key, $title ) = explode( '|', $key );
			} else{
				$title = isset( $this->field[$key] ) ? $this->field[$key] : $key;
			}
			// 场景检测
			if( !empty( $this->only ) && !in_array( $key, $this->only ) ){
				continue;
			}
			// 获取数据 支持二维数组
			$value = $this->getDataValue( $data, $key );

			// 字段验证
			if( $rule instanceof \Closure ){
				$result = call_user_func_array( $rule, [$value, $data] );
			} elseif( $rule instanceof ValidatorRule ){
				//  验证因子
				$result = $this->checkItem( $key, $value, $rule->getRule(), $data, $rule->getTitle() ?: $title, $rule->getMsg() );
			} else{
				$result = $this->checkItem( $key, $value, $rule, $data, $title );
			}
			if( true !== $result ){
				// 没有返回true 则表示验证失败
				if( !empty( $this->batch ) ){
					// 批量验证
					if( is_array( $result ) ){
						$this->error = array_merge( $this->error, $result );
					} else{
						$this->error[$key] = $result;
					}
				} else{
					$this->error = $result;
					return false;
				}
			}
		}
		return !empty( $this->error ) ? false : true;
	}

	/**
	 * 根据验证规则验证数据
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rules 验证规则
	 * @return bool
	 */
	public function checkRule( $value, $rules )
	{
		if( $rules instanceof \Closure ){
			return call_user_func_array( $rules, [$value] );
		} elseif( $rules instanceof ValidatorRule ){
			$rules = $rules->getRule();
		} elseif( is_string( $rules ) ){
			$rules = explode( '|', $rules );
		}
		foreach( $rules as $key => $rule ){
			if( $rule instanceof \Closure ){
				$result = call_user_func_array( $rule, [$value] );
			} else{
				// 判断验证类型
				list( $type, $rule ) = $this->getValidatorType( $key, $rule );
				$callback = isset( self::$type[$type] ) ? self::$type[$type] : [$this, $type];
				$result   = call_user_func_array( $callback, [$value, $rule] );
			}
			if( true !== $result ){
				return $result;
			}
		}
		return true;
	}


	/**
	 * 验证单个字段规则
	 * @param  string $field 字段名
	 * @param  mixed  $value 字段值
	 * @param  mixed  $rules 验证规则
	 * @param  array  $data  数据
	 * @param  string $title 字段描述
	 * @param  array  $msg   提示信息
	 * @return mixed
	 */
	protected function checkItem( $field, $value, $rules, $data, $title = '', $msg = [] )
	{
		if( isset( $this->remove[$field] ) && true === $this->remove[$field] && empty( $this->append[$field] ) ){
			// 字段已经移除 无需验证
			return true;
		}
		// 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...]
		if( is_string( $rules ) ){
			$rules = explode( '|', $rules );
		}
		if( isset( $this->append[$field] ) ){
			// 追加额外的验证规则
			$rules = array_merge( $rules, $this->append[$field] );
		}
		$lang = require_once "lang/zh-cn.php";
		$i    = 0;
		foreach( $rules as $key => $rule ){
			if( $rule instanceof \Closure ){
				$result = call_user_func_array( $rule, [$value, $data] );
				$info   = is_numeric( $key ) ? '' : $key;
			} else{
				// 判断验证类型
				list( $type, $rule, $info ) = $this->getValidatorType( $key, $rule );
				if( isset( $this->append[$field] ) && in_array( $info, $this->append[$field] ) ){
				} elseif( isset( $this->remove[$field] ) && in_array( $info, $this->remove[$field] ) ){
					// 规则已经移除
					$i ++;
					continue;
				}
				if( 'must' == $info || 0 === strpos( $info, 'require' ) || (!is_null( $value ) && '' !== $value) ){
					// 验证类型
					$callback = isset( self::$type[$type] ) ? self::$type[$type] : [$this, $type];
					// 验证数据
					$result = call_user_func_array( $callback, [$value, $rule, $data, $field, $title] );
				} else{
					$result = true;
				}
			}
			if( false === $result ){
				// 验证失败 返回错误信息
				if( !empty( $msg[$i] ) ){
					$message = $msg[$i];
					if( is_string( $message ) && strpos( $message, '{%' ) === 0 ){
						$message = $lang[substr( $message, 2, - 1 )];
					}
				} else{
					$message = $this->getRuleMsg( $field, $title, $info, $rule );
				}
				return $message;
			} elseif( true !== $result ){
				// 返回自定义错误信息
				if( is_string( $result ) && false !== strpos( $result, ':' ) ){
					$result = str_replace( [':attribute', ':rule'], [$title, (string)$rule], $result );
				}
				return $result;
			}
			$i ++;
		}
		return $result;
	}

	/**
	 * 获取当前验证类型及规则
	 * @access public
	 * @param  mixed $key
	 * @param  mixed $rule
	 * @return array
	 */
	protected function getValidatorType( $key, $rule )
	{
		// 判断验证类型
		if( !is_numeric( $key ) ){
			return [$key, $rule, $key];
		}
		if( strpos( $rule, ':' ) ){
			list( $type, $rule ) = explode( ':', $rule, 2 );
			if( isset( $this->alias[$type] ) ){
				// 判断别名
				$type = $this->alias[$type];
			}
			$info = $type;
		} elseif( method_exists( $this, $rule ) ){
			$type = $rule;
			$info = $rule;
			$rule = '';
		} else{
			$type = 'is';
			$info = $rule;
		}
		return [$type, $rule, $info];
	}

	/**
	 * 验证是否和某个字段的值一致
	 * @access public
	 * @param  mixed  $value 字段值
	 * @param  mixed  $rule  验证规则
	 * @param  array  $data  数据
	 * @param  string $field 字段名
	 * @return bool
	 */
	public function confirm( $value, $rule, $data = [], $field = '' )
	{
		if( '' == $rule ){
			if( strpos( $field, '_confirm' ) ){
				$rule = strstr( $field, '_confirm', true );
			} else{
				$rule = $field.'_confirm';
			}
		}
		return $this->getDataValue( $data, $rule ) === $value;
	}

	/**
	 * 验证是否和某个字段的值是否不同
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @param  array $data  数据
	 * @return bool
	 */
	public function different( $value, $rule, $data = [] )
	{
		return $this->getDataValue( $data, $rule ) != $value;
	}

	/**
	 * 验证是否大于等于某个值
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @param  array $data  数据
	 * @return bool
	 */
	public function egt( $value, $rule, $data = [] )
	{
		return $value >= $this->getDataValue( $data, $rule );
	}

	/**
	 * 验证是否大于某个值
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @param  array $data  数据
	 * @return bool
	 */
	public function gt( $value, $rule, $data )
	{
		return $value > $this->getDataValue( $data, $rule );
	}

	/**
	 * 验证是否小于等于某个值
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @param  array $data  数据
	 * @return bool
	 */
	public function elt( $value, $rule, $data = [] )
	{
		return $value <= $this->getDataValue( $data, $rule );
	}

	/**
	 * 验证是否小于某个值
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @param  array $data  数据
	 * @return bool
	 */
	public function lt( $value, $rule, $data = [] )
	{
		return $value < $this->getDataValue( $data, $rule );
	}

	/**
	 * 验证是否等于某个值
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @return bool
	 */
	public function eq( $value, $rule )
	{
		return $value == $rule;
	}

	/**
	 * 必须验证
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @return bool
	 */
	public function must( $value, $rule = null )
	{
		return !empty( $value ) || '0' == $value;
	}

	/**
	 * 验证字段值是否为有效格式
	 * @access public
	 * @param  mixed  $value 字段值
	 * @param  string $rule  验证规则
	 * @param  array  $data  验证数据
	 * @return bool
	 */
	public function is( $value, $rule, $data = [] )
	{
		switch( Loader::parseName( $rule, 1, false ) ){
		case 'require':
			// 必须
			$result = !empty( $value ) || '0' == $value;
		break;
		case 'accepted':
			// 接受
			$result = in_array( $value, ['1', 'on', 'yes'] );
		break;
		case 'date':
			// 是否是一个有效日期
			$result = false !== strtotime( $value );
		break;
		case 'activeUrl':
			// 是否为有效的网址
			$result = checkdnsrr( $value );
		break;
		case 'boolean':
		case 'bool':
			// 是否为布尔值
			$result = in_array( $value, [true, false, 0, 1, '0', '1'], true );
		break;
		case 'number':
			$result = is_numeric( $value );
		break;
		case 'array':
			// 是否为数组
			$result = is_array( $value );
		break;
		case 'file':
			$result = isset( $value['name'] ) && isset( $value['type'] ) && isset( $value['tmp_name'] ) && isset( $value['error'] ) && isset( $value['size'] );
		break;
		case 'image':
			$result = isset( $value['name'] ) && isset( $value['type'] ) && isset( $value['tmp_name'] ) && isset( $value['error'] ) && isset( $value['size'] ) && in_array( $this->getImageType( $value->getRealPath() ), [1, 2, 3, 6] );
		break;
		case 'token':
			$result = $this->token( $value, '__token__', $data );
		break;
		case 'integer':
			// 是否为整型
			$result = $this->filter( $value, FILTER_VALIDATE_INT );
		break;
		case 'json':
			// 是否为json
			$result = $this->json( $value );
		break;
		case 'phone':
			$result = $this->phone( $value );
		break;
			//TODO email
		default:
			if( isset( self::$type[$rule] ) ){
				// 注册的验证规则
				$result = call_user_func_array( self::$type[$rule], [$value] );
			} elseif( isset( $this->filter[$rule] ) ){
				// Filter_var验证规则
				$result = $this->filter( $value, $this->filter[$rule] );
			} else{
				// 正则验证
				$result = $this->regex( $value, $rule );
			}
		}
		return $result;
	}

	// 判断图像类型
	protected function getImageType( $image )
	{
		if( function_exists( 'exif_imagetype' ) ){
			return exif_imagetype( $image );
		} else{
			try{
				$info = getimagesize( $image );
				return $info ? $info[2] : false;
			} catch( \Exception $e ){
				return false;
			}
		}
	}

	/**
	 * 验证是否为合格的域名或者IP 支持A，MX，NS，SOA，PTR，CNAME，AAAA，A6， SRV，NAPTR，TXT 或者 ANY类型
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @return bool
	 */
	public function activeUrl( $value, $rule = 'MX' )
	{
		if( !in_array( $rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'] ) ){
			$rule = 'MX';
		}
		return checkdnsrr( $value, $rule );
	}

	/**
	 * 验证是否有效IP
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则 ipv4 ipv6
	 * @return bool
	 */
	public function ip( $value, $rule = 'ipv4' )
	{
		if( !in_array( $rule, ['ipv4', 'ipv6'] ) ){
			$rule = 'ipv4';
		}
		return $this->filter( $value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4] );
	}

	/**
	 * 验证上传文件后缀
	 * @access public
	 * @param  mixed $file 上传文件
	 * @param  mixed $rule 验证规则
	 * @return bool
	 */
	public function fileExt( $file, $rule )
	{
		if( is_array( $file ) ){
			foreach( $file as $item ){
				if( !($item instanceof File) || !$item->checkExt( $rule ) ){
					return false;
				}
			}
			return true;
		} elseif( $file instanceof File ){
			return $file->checkExt( $rule );
		} else{
			return false;
		}
	}

	/**
	 * 验证上传文件类型
	 * @access public
	 * @param  mixed $file 上传文件
	 * @param  mixed $rule 验证规则
	 * @return bool
	 */
	public function fileMime( $file, $rule )
	{
		if( is_array( $file ) ){
			foreach( $file as $item ){
				if( !($item instanceof File) || !$item->checkMime( $rule ) ){
					return false;
				}
			}
			return true;
		} elseif( $file instanceof File ){
			return $file->checkMime( $rule );
		} else{
			return false;
		}
	}

	/**
	 * 验证上传文件大小
	 * @access public
	 * @param  mixed $file 上传文件
	 * @param  mixed $rule 验证规则
	 * @return bool
	 */
	public function fileSize( $file, $rule )
	{
		if( is_array( $file ) ){
			foreach( $file as $item ){
				if( !($item instanceof File) || !$item->checkSize( $rule ) ){
					return false;
				}
			}
			return true;
		} elseif( $file instanceof File ){
			return $file->checkSize( $rule );
		} else{
			return false;
		}
	}

	/**
	 * 验证图片的宽高及类型
	 * @access public
	 * @param  mixed $file 上传文件
	 * @param  mixed $rule 验证规则
	 * @return bool
	 */
	public function image( $file, $rule )
	{
		if( !($file instanceof File) ){
			return false;
		}
		if( $rule ){
			$rule = explode( ',', $rule );
			list( $width, $height, $type ) = getimagesize( $file->getRealPath() );
			if( isset( $rule[2] ) ){
				$imageType = strtolower( $rule[2] );
				if( 'jpeg' == $imageType ){
					$imageType = 'jpg';
				}
				if( image_type_to_extension( $type, false ) != $imageType ){
					return false;
				}
			}
			list( $w, $h ) = $rule;
			return $w == $width && $h == $height;
		} else{
			return in_array( $this->getImageType( $file->getRealPath() ), [1, 2, 3, 6] );
		}
	}


	/**
	 * 验证时间和日期是否符合指定格式
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @return bool
	 */
	public function dateFormat( $value, $rule )
	{
		$info = date_parse_from_format( $rule, $value );
		return 0 == $info['warning_count'] && 0 == $info['error_count'];
	}

	/**
	 * 使用filter_var方式验证
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @return bool
	 */
	public function filter( $value, $rule )
	{
		if( is_string( $rule ) && strpos( $rule, ',' ) ){
			list( $rule, $param ) = explode( ',', $rule );
		} elseif( is_array( $rule ) ){
			$param = isset( $rule[1] ) ? $rule[1] : null;
			$rule  = $rule[0];
		} else{
			$param = null;
		}
		return false !== filter_var( $value, is_int( $rule ) ? $rule : filter_id( $rule ), $param );
	}

	/**
	 * 验证某个字段等于某个值的时候必须
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @param  array $data  数据
	 * @return bool
	 */
	public function requireIf( $value, $rule, $data )
	{
		list( $field, $val ) = explode( ',', $rule );
		if( $this->getDataValue( $data, $field ) == $val ){
			return !empty( $value ) || '0' == $value;
		} else{
			return true;
		}
	}

	/**
	 * 通过回调方法验证某个字段是否必须
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @param  array $data  数据
	 * @return bool
	 */
	public function requireCallback( $value, $rule, $data )
	{
		$result = call_user_func_array( $rule, [$value, $data] );
		if( $result ){
			return !empty( $value ) || '0' == $value;
		} else{
			return true;
		}
	}

	/**
	 * 验证某个字段有值的情况下必须
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @param  array $data  数据
	 * @return bool
	 */
	public function requireWith( $value, $rule, $data )
	{
		$val = $this->getDataValue( $data, $rule );
		if( !empty( $val ) ){
			return !empty( $value ) || '0' == $value;
		} else{
			return true;
		}
	}

	/**
	 * 验证是否在范围内
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @return bool
	 */
	public function in( $value, $rule )
	{
		return in_array( $value, is_array( $rule ) ? $rule : explode( ',', $rule ) );
	}

	/**
	 * 验证是否不在某个范围
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @return bool
	 */
	public function notIn( $value, $rule )
	{
		return !in_array( $value, is_array( $rule ) ? $rule : explode( ',', $rule ) );
	}

	/**
	 * between验证数据
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @return bool
	 */
	public function between( $value, $rule )
	{
		if( is_string( $rule ) ){
			$rule = explode( ',', $rule );
		}
		list( $min, $max ) = $rule;
		return $value >= $min && $value <= $max;
	}

	/**
	 * 使用notbetween验证数据
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @return bool
	 */
	public function notBetween( $value, $rule )
	{
		if( is_string( $rule ) ){
			$rule = explode( ',', $rule );
		}
		list( $min, $max ) = $rule;
		return $value < $min || $value > $max;
	}

	/**
	 * 验证数据长度
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @return bool
	 */
	public function length( $value, $rule )
	{
		if( is_array( $value ) ){
			$length = count( $value );
		} elseif( $value instanceof File ){
			$length = $value->getSize();
		} else{
			$length = mb_strlen( (string)$value );
		}
		if( strpos( $rule, ',' ) ){
			// 长度区间
			list( $min, $max ) = explode( ',', $rule );
			return $length >= $min && $length <= $max;
		} else{
			// 指定长度
			return $length == $rule;
		}
	}

	/**
	 * 验证数据最大长度
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @return bool
	 */
	public function max( $value, $rule )
	{
		if( is_array( $value ) ){
			$length = count( $value );
		} elseif( $value instanceof File ){
			$length = $value->getSize();
		} else{
			$length = mb_strlen( (string)$value );
		}
		return $length <= $rule;
	}

	/**
	 * 验证数据最小长度
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @return bool
	 */
	public function min( $value, $rule )
	{
		if( is_array( $value ) ){
			$length = count( $value );
		} elseif( $value instanceof File ){
			$length = $value->getSize();
		} else{
			$length = mb_strlen( (string)$value );
		}
		return $length >= $rule;
	}

	/**
	 * 验证日期
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @return bool
	 */
	public function after( $value, $rule )
	{
		return strtotime( $value ) >= strtotime( $rule );
	}

	/**
	 * 验证日期
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则
	 * @return bool
	 */
	public function before( $value, $rule )
	{
		return strtotime( $value ) <= strtotime( $rule );
	}

	/**
	 * 验证IP许可
	 * @access public
	 * @param  string $value 字段值
	 * @param  mixed  $rule  验证规则
	 * @return mixed
	 */
	public function allowIp( $value, $rule )
	{
		return in_array( $value, is_array( $rule ) ? $rule : explode( ',', $rule ) );
	}

	/**
	 * 验证IP禁用
	 * @access public
	 * @param  string $value 字段值
	 * @param  mixed  $rule  验证规则
	 * @return mixed
	 */
	public function denyIp( $value, $rule )
	{
		return !in_array( $value, is_array( $rule ) ? $rule : explode( ',', $rule ) );
	}

	/**
	 * 使用正则验证数据
	 * @access public
	 * @param  mixed $value 字段值
	 * @param  mixed $rule  验证规则 正则规则或者预定义正则名
	 * @return bool
	 */
	public function regex( $value, $rule )
	{
		if( isset( $this->regex[$rule] ) ){
			$rule = $this->regex[$rule];
		}
		if( 0 !== strpos( $rule, '/' ) && !preg_match( '/\/[imsU]{0,4}$/', $rule ) ){
			// 不是正则表达式则两端补上/
			$rule = '/^'.$rule.'$/';
		}
		return 1 === preg_match( $rule, (string)$value );
	}


	// 获取错误信息
	public function getError()
	{
		return $this->error;
	}

	/**
	 * 获取数据值
	 * @param  array  $data 数据
	 * @param  string $key  数据标识 支持二维
	 * @return mixed
	 */
	protected function getDataValue( $data, $key )
	{
		if( is_numeric( $key ) ){
			$value = $key;
		} elseif( strpos( $key, '.' ) ){
			// 支持二维数组验证
			list( $name1, $name2 ) = explode( '.', $key );
			$value = isset( $data[$name1][$name2] ) ? $data[$name1][$name2] : null;
		} else{
			$value = isset( $data[$key] ) ? $data[$key] : null;
		}
		return $value;
	}

	/**
	 * 获取验证规则的错误提示信息
	 * @param  string $attribute 字段英文名
	 * @param  string $title     字段描述名
	 * @param  string $type      验证规则名称
	 * @param  mixed  $rule      验证规则数据
	 * @return string
	 */
	protected function getRuleMsg( $attribute, $title, $type, $rule )
	{
		$lang = require_once "lang/zh-cn.php";
		if( isset( $this->message[$attribute.'.'.$type] ) ){
			$msg = $this->message[$attribute.'.'.$type];
		} elseif( isset( $this->message[$attribute][$type] ) ){
			$msg = $this->message[$attribute][$type];
		} elseif( isset( $this->message[$attribute] ) ){
			$msg = $this->message[$attribute];
		} elseif( isset( self::$typeMsg[$type] ) ){
			$msg = self::$typeMsg[$type];
		} elseif( 0 === strpos( $type, 'require' ) ){
			$msg = self::$typeMsg['require'];
		} else{
			$msg = $title.$lang['not conform to the rules'];
		}
		if( is_string( $msg ) && 0 === strpos( $msg, '{%' ) ){
			$msg = $lang[substr( $msg, 2, - 1 )];
		} elseif( isset( $lang[$msg] ) ){
			$msg = $lang[$msg];
		}
		if( is_string( $msg ) && is_scalar( $rule ) && false !== strpos( $msg, ':' ) ){
			// 变量替换
			if( is_string( $rule ) && strpos( $rule, ',' ) ){
				$array = array_pad( explode( ',', $rule ), 3, '' );
			} else{
				$array = array_pad( [], 3, '' );
			}
			$msg = str_replace( [':attribute', ':rule', ':1', ':2', ':3'], [
				$title,
				(string)$rule,
				$array[0],
				$array[1],
				$array[2],
			], $msg );
		}
		return $msg;
	}

	/**
	 * 获取数据验证的场景
	 * @param  string $scene 验证场景
	 * @return array
	 */
	protected function getScene( string $scene = '' )
	{
		if( empty( $scene ) ){
			$scene = $this->currentSceneName;
		}
		$this->only = $this->append = $this->remove = [];
		if( empty( $scene ) ){
			return null;
		}
		if( method_exists( $this, 'scene'.$scene ) ){
			call_user_func( [$this, 'scene'.$scene] );
		} elseif( isset( $this->scene[$scene] ) ){
			// 如果设置了验证适用场景
			$scene = $this->scene[$scene];
			if( is_string( $scene ) ){
				$scene = explode( ',', $scene );
			}
			$this->only = $scene;
		}
	}

	/**
	 * 动态方法 直接调用is方法进行验证
	 * @access public
	 * @param  string $method 方法名
	 * @param  array  $args   调用参数
	 * @return bool
	 */
	public function __call( $method, $args )
	{
		if( 'is' == strtolower( substr( $method, 0, 2 ) ) ){
			$method = substr( $method, 2 );
		}
		array_push( $args, lcfirst( $method ) );
		return call_user_func_array( [$this, 'is'], $args );
	}


	/**
	 * 手机格式
	 * @param mixed $value 字段值
	 * @param mixed $rule  验证规则
	 * @return bool
	 */
	protected function phone( $value, $rule = 'zh' )
	{
		// 发现一些未知手机号 不能确定
		if( preg_match( "/^1[234567890]{1}\d{9}$/", $value ) ){
			return true;
		} else{
			return false;
		}
	}

	/**
	 * 数组是否有重复值
	 * @param mixed $value 字段值
	 * @param mixed $rule  验证规则
	 * @return bool
	 */
	protected function arrayUnique( $value )
	{
		if( count( $value ) === count( array_unique( $value ) ) ){
			return true;
		} else{
			return false;
		}
	}


	protected function integer( $value )
	{
		return $this->is( $value, 'integer' );
	}

	protected function date( $value )
	{
		return $this->is( $value, 'date' );
	}


	/**
	 * todo 老了
	 * 验证json
	 * @param mixed $value 字段值
	 * @return bool
	 */
	protected function json( $value )
	{
		if( !is_string( $value ) || '' === $value ){
			return false;
		}
		json_decode( $value );
		return (json_last_error() === JSON_ERROR_NONE);
	}

	public function getRule()
	{
		return $this->rule;
	}

	public static function __callStatic( $method, $params )
	{
		$class = self::make();
		if( method_exists( $class, $method ) ){
			return call_user_func_array( [$class, $method], $params );
		} else{
			throw new \BadMethodCallException( 'method not exists:'.__CLASS__.'->'.$method );
		}
	}

}